Skip to content

Feature/#88 notification page link feat#104

Merged
yiyoonseo merged 4 commits into
developfrom
feature/#88-notification_page_link-feat
Jul 25, 2026
Merged

Feature/#88 notification page link feat#104
yiyoonseo merged 4 commits into
developfrom
feature/#88-notification_page_link-feat

Conversation

@yiyoonseo

@yiyoonseo yiyoonseo commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

🔗 관련 이슈

#88

📝 개요

  • 알림 및 토스트 연동
  • 에러 및 예외 처리

⌨️ 작업 상세 내용

1. 공고 입력 및 분석 에러 예외 처리

백엔드 분석 데이터 누락/실패 방어: AI 분석 결과나 jobPostingId가 유실되었거나 정상 저장되지 않았을 경우, 엉뚱한 리뷰 페이지로 라우팅되지 않고 생성 페이지로 리다이렉트되도록 방어 로직 추가

사용자 안내 모달 도입: analysisError=not_saved 파라미터를 감지하여, 유저가 빈 결과 화면 대신 "자세하게 다시 작성해 주세요"라는 안내 모달(단일 확인 버튼)을 마주하도록 예외 처리 구현

2. 자소서 분석 로딩 및 실패 예외 처리

로딩 화면 유지형 에러 모달: 자소서 분석 중 타임아웃이나 에러 발생 시, 페이지를 이탈하지 않고 로딩 화면 위에 블러 오버레이와 함께 "나중에 다시 시도해주세요" 에러 모달이 고정되어 노출되도록 개선

불필요한 폴링 차단: 에러 상태(error=true)로 진입한 경우 백그라운드 API 폴링(상태 조회) 요청이 실행되지 않도록 차단 로직 추가

3. 알림(LNB) 라우팅 및 연동 보완

알림 연동 타입 분기: 공고 및 자소서 분석의 성공/실패(JOB_POSTING_ASYNC_FAILED, ANALYSIS_ASYNC_FAILED 등) 상태에 맞추어 올바른 에러 쿼리 파라미터(error=true, analysisError)와 함께 적절한 페이지로 라우팅되도록 연동

파라미터 누락 방지: 알림 클릭 시 필요한 jobPostingId 및 식별 값들이 올바르게 전달되도록 라우팅 파라미터 구조 정비

💡 코드 설명 및 참고사항

📸 스크린샷 (UI 변경 시)

image image

🔍 리뷰 요구사항 (Reviewers)

  • [ ]

⚠️ 로컬 실행 시 유의사항

Summary by CodeRabbit

  • New Features

    • Added real-time notification updates and toast alerts.
    • Notification links now route to the appropriate review, analysis, or recovery flow.
    • Added clearer handling for unsaved job-posting analysis results.
    • Added loading and error states for resume analysis.
  • Bug Fixes

    • Improved notification refresh and unread-status handling.
    • Prevented redirects after pages are unmounted.
    • Added an alert when job-posting data cannot be loaded.
    • Improved loading and review-page initialization.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Job-posting analysis now distinguishes saved and unsaved results, notification handling uses authenticated SSE with refreshed API data and toast feedback, and resume-analysis failures propagate through query parameters into a non-retrying notice flow.

Changes

Notification contract and stream

Layer / File(s) Summary
Notification payload and SSE subscription
jobdri/src/lib/api/notification.ts, jobdri/src/app/mockApply/.../jd-review/page.tsx
Notification metadata types, uncached fetching, and authenticated SSE parsing and cleanup are updated.

Notification UI and routing

Layer / File(s) Summary
LNB refresh and toast behavior
jobdri/src/components/common/lnb/Lnb.tsx, jobdri/src/lib/api/notification.ts
SSE events trigger notification refetches, unread arrivals can display top-positioned toasts, and duplicate subscriptions are guarded.
Notification mapping and navigation
jobdri/src/components/common/lnb/LnbNotification.tsx
Job-posting and analysis metadata are mapped into notification items, with routing based on save status, posting IDs, task IDs, and mock-apply IDs.

Job-posting analysis flow

Layer / File(s) Summary
Analysis completion and review loading
jobdri/src/app/mockApply/job/loading/page.tsx, jobdri/src/app/mockApply/job/[jobPostingId]/review/page.tsx
Analysis redirects to review only for saved results; the review page loads URL-selected postings asynchronously and saves updated form state.
Unsaved-result notice
jobdri/src/app/mockApply/job/create/page.tsx
The create page handles analysisError=not_saved with a blocking invalid-data notice modal.

Resume-analysis error flow

Layer / File(s) Summary
Error query propagation and notice modal
jobdri/src/app/mockApply/[mockApplyId]/result/resume-analysis-loading/page.tsx, ResumeAnalysisLoadingPageClient.tsx
An error=true query parameter stops processing, sets a credit-recovery message, and changes the modal to a single confirmation action.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: ⭐ Feature, 🩵 민교

Suggested reviewers: minnngo

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The home page comment cleanup is unrelated to the notification API integration work. Remove the unrelated page.tsx cleanup or move it to a separate PR so this change stays focused on notification API integration.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is related to the notification API and routing changes, though it is a bit noisy.
Linked Issues check ✅ Passed The changes implement notification API integration with stream handling, routing, and toast updates.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#88-notification_page_link-feat

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
jobdri/src/lib/api/notification.ts (1)

91-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the commented-out debug block.

♻️ Proposed cleanup
       onmessage(event) {
-        // console.log(
-        //   "📥 [SSE 통신 감지!!] 서버가 보낸 원본 데이터:",
-        //   event.data,
-        // );
-
         if (!event.data || !event.data.trim().startsWith("{")) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/lib/api/notification.ts` around lines 91 - 95, Remove the
commented-out console.log debug block near the SSE event handling code, leaving
the surrounding notification logic unchanged.
jobdri/src/components/common/lnb/LnbNotification.tsx (1)

428-436: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

taskId is destructured but never used in the switch below — drop it to avoid an unused-variable lint failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/components/common/lnb/LnbNotification.tsx` around lines 428 - 436,
Remove taskId from the notificationItem destructuring in LnbNotification so only
fields used by the switch and surrounding logic remain.
jobdri/src/components/common/lnb/Lnb.tsx (2)

168-169: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Toast timer is never cleared.

Back-to-back notifications stack timers, so an earlier timeout can dismiss a newer toast, and nothing cancels the pending timer on unmount. Store the handle in a ref and clear it before scheduling and in the effect cleanup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/components/common/lnb/Lnb.tsx` around lines 168 - 169, Update the
toast state flow in Lnb to store the setTimeout handle in a ref, clear any
existing timer before scheduling a new dismissal, and clear the pending timer
during effect cleanup on unmount. Preserve the current 3000ms dismissal behavior
while preventing older notifications from dismissing newer toasts.

108-110: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

hasSubscribedRef guard doesn't achieve its stated goal.

The cleanup resets the ref to false, so the Strict Mode double-mount it's meant to block will still re-subscribe on the second mount. With an empty dep array the effect already runs once per mount; consider dropping the ref, or keep it module-scoped if you truly need a single global stream per session.

Also applies to: 186-189

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/components/common/lnb/Lnb.tsx` around lines 108 - 110, The
hasSubscribedRef guard in the Lnb useEffect does not prevent Strict Mode
re-subscription because cleanup resets it. Remove the ref guard and its reset,
relying on the empty dependency array for one subscription per mount, or move
the guard to module scope only if a single stream must be shared globally.
jobdri/src/app/mockApply/[mockApplyId]/result/resume-analysis-loading/ResumeAnalysisLoadingPageClient.tsx (1)

269-298: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

handleRetry appears orphaned after collapsing the modal to a single action.

The modal (Lines 289-298) now only wires a single "확인" action to moveBackToResume; handleRetry (resets errorMessage/pollingRetryKey) is no longer referenced by any control in the render. If dropping the retry path for all error states (not just the timeout/isError case) is intentional, remove handleRetry; otherwise re-wire it as a secondary action for the recoverable error paths (e.g., transient polling failures) while keeping the single-action modal for the isError/timeout case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@jobdri/src/app/mockApply/`[mockApplyId]/result/resume-analysis-loading/ResumeAnalysisLoadingPageClient.tsx
around lines 269 - 298, Resolve the orphaned handleRetry function in the
ResumeAnalysisLoadingPageClient render flow: either remove handleRetry and its
retry state usage if all error states intentionally exit through
moveBackToResume, or wire it as a secondary action only for recoverable
transient polling failures while preserving the single-action modal for the
isError/timeout case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@jobdri/src/app/mockApply/`[mockApplyId]/(jd)/jd-review/page.tsx:
- Around line 31-97: Remove the accidentally added subscribeToNotificationStream
helper and its SSE-related imports or references from this page. Restore a local
subscribeToSessionStorage function for the useSyncExternalStore call, preserving
sessionStorage change notifications and the existing snapshot behavior. Keep
notification SSE functionality in the existing notification API module instead.

In `@jobdri/src/components/common/lnb/Lnb.tsx`:
- Line 176: Remove or minimize the full notification payload logging in Lnb.tsx
at jobdri/src/components/common/lnb/Lnb.tsx:176-176, retaining only the
notification type or identifier if needed. In LnbNotification.tsx at
jobdri/src/components/common/lnb/LnbNotification.tsx:426-426, remove the
click-time full-object log or gate it behind a development-only check.
- Around line 112-131: Move the toast side effect out of the
setNotificationItems updater in fetchAndUpdateNotifications. Track the
previously seen latest notification id with a latestNotificationIdRef ref,
compare it with mappedItems[0] outside the state setter, trigger the toast only
for a new unread latest item, then update the ref and set mappedItems without
side effects.

In `@jobdri/src/components/common/lnb/LnbNotification.tsx`:
- Around line 443-457: The JOB_POSTING_ASYNC_SUCCEEDED deferred-save and
JOB_POSTING_ASYNC_FAILED paths use error query values that do not match the
consumer’s modal trigger. Define or reuse one shared analysisError constant for
the not-saved case, update both router.push calls in LnbNotification
accordingly, and ensure the /mockApply/job/create consumer uses the same
constant.

---

Nitpick comments:
In
`@jobdri/src/app/mockApply/`[mockApplyId]/result/resume-analysis-loading/ResumeAnalysisLoadingPageClient.tsx:
- Around line 269-298: Resolve the orphaned handleRetry function in the
ResumeAnalysisLoadingPageClient render flow: either remove handleRetry and its
retry state usage if all error states intentionally exit through
moveBackToResume, or wire it as a secondary action only for recoverable
transient polling failures while preserving the single-action modal for the
isError/timeout case.

In `@jobdri/src/components/common/lnb/Lnb.tsx`:
- Around line 168-169: Update the toast state flow in Lnb to store the
setTimeout handle in a ref, clear any existing timer before scheduling a new
dismissal, and clear the pending timer during effect cleanup on unmount.
Preserve the current 3000ms dismissal behavior while preventing older
notifications from dismissing newer toasts.
- Around line 108-110: The hasSubscribedRef guard in the Lnb useEffect does not
prevent Strict Mode re-subscription because cleanup resets it. Remove the ref
guard and its reset, relying on the empty dependency array for one subscription
per mount, or move the guard to module scope only if a single stream must be
shared globally.

In `@jobdri/src/components/common/lnb/LnbNotification.tsx`:
- Around line 428-436: Remove taskId from the notificationItem destructuring in
LnbNotification so only fields used by the switch and surrounding logic remain.

In `@jobdri/src/lib/api/notification.ts`:
- Around line 91-95: Remove the commented-out console.log debug block near the
SSE event handling code, leaving the surrounding notification logic unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b463a8ba-284f-4e1c-88ee-bb7325701aac

📥 Commits

Reviewing files that changed from the base of the PR and between 4971576 and f3741bc.

📒 Files selected for processing (10)
  • jobdri/src/app/mockApply/[mockApplyId]/(jd)/jd-review/page.tsx
  • jobdri/src/app/mockApply/[mockApplyId]/result/resume-analysis-loading/ResumeAnalysisLoadingPageClient.tsx
  • jobdri/src/app/mockApply/[mockApplyId]/result/resume-analysis-loading/page.tsx
  • jobdri/src/app/mockApply/job/[jobPostingId]/review/page.tsx
  • jobdri/src/app/mockApply/job/create/page.tsx
  • jobdri/src/app/mockApply/job/loading/page.tsx
  • jobdri/src/app/page.tsx
  • jobdri/src/components/common/lnb/Lnb.tsx
  • jobdri/src/components/common/lnb/LnbNotification.tsx
  • jobdri/src/lib/api/notification.ts

Comment on lines +31 to 97
export function subscribeToNotificationStream(
onMessage: (data: ApiNotificationItem) => void,
onError?: (error: unknown) => void,
) {
const headers = getAuthHeaders();
const ctrl = new AbortController();

if (!headers.Authorization) {
console.warn("로그인 토큰이 없어 실시간 알림을 연결하지 않습니다.");
return () => ctrl.abort();
}

window.addEventListener("storage", handleStorage);
const tokenOnly = headers.Authorization.replace("Bearer ", "");

return () => {
window.removeEventListener("storage", handleStorage);
};
console.log(
"🔌 SSE 연결 시도 중... URL:",
`${API_BASE_URL}/api/notifications/stream`,
);

fetchEventSource(
`${API_BASE_URL}/api/notifications/stream?accessToken=${tokenOnly}`,
{
method: "GET",
headers: {
...headers,
Accept: "text/event-stream",
},
signal: ctrl.signal,

onopen(response) {
if (response.ok) {
console.log("🟢 SSE 연결 성공!! 서버와 스트림 연결됨.");
} else {
console.error(
"🔴 SSE 연결 응답 이상:",
response.status,
response.statusText,
);
}
},

onmessage(event) {
console.log("📥 SSE raw 메시지 수신:", event.data); // 👈 서버가 뭐라도 보내면 무조건 찍힘!

if (!event.data || !event.data.trim().startsWith("{")) {
return;
}

try {
const parsedData = JSON.parse(event.data) as ApiNotificationItem;
onMessage(parsedData);
} catch (e: unknown) {
console.error("SSE 데이터 파싱 실패:", e);
}
},

onerror(err: unknown) {
console.error("❌ SSE 스트림 에러 발생:", err);
if (onError) onError(err);
},
},
).catch((err) => {
console.error("❌ SSE 연결 래퍼 예외 발생:", err);
});

return () => ctrl.abort();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t f 'page.tsx' -p 'jd-review' --exec ast-grep outline {} --items all
rg -nP '\bsubscribeToSessionStorage\b' --type=ts --type=tsx
rg -nP '\b(getAuthHeaders|API_BASE_URL|fetchEventSource|ApiNotificationItem)\b' -g '**/jd-review/page.tsx'

Repository: JobDri-Developer/FrontEnd

Length of output: 288


🏁 Script executed:

#!/bin/bash
set -u

echo "== candidate file =="
git ls-files | grep -F 'jobdri/src/app/mockApply/[mockApplyId]/(jd)/jd-review/page.tsx' || true

echo "== file excerpt =="
cat -n 'jobdri/src/app/mockApply/[mockApplyId]/(jd)/jd-review/page.tsx' | sed -n '1,120p'
echo "--- line 230-260 ---"
cat -n 'jobdri/src/app/mockApply/[mockApplyId]/(jd)/jd-review/page.tsx' | sed -n '230,260p'

echo "== references =="
git ls-files | grep -E '\.(ts|tsx)$' | xargs grep -n 'subscribeToSessionStorage' 2>/dev/null || true
echo "== target identifiers in jd-review page =="
git ls-files | grep '/jd-review/page.tsx$' | xargs grep -n 'getAuthHeaders\|API_BASE_URL\|fetchEventSource\|ApiNotificationItem' 2>/dev/null || true

echo "== notification helper excerpt =="
cat -n jobdri/src/lib/api/notification.ts | sed -n '1,150p'

Repository: JobDri-Developer/FrontEnd

Length of output: 10443


Remove the accidentally pasted SSE helper from this page and restore subscribeToSessionStorage.

page.tsx now exports subscribeToNotificationStream, which references undefined getAuthHeaders, API_BASE_URL, fetchEventSource, and ApiNotificationItem; meanwhile useSyncExternalStore at line 247 still calls the missing subscribeToSessionStorage. Keep the notification SSE helper in jobdri/src/lib/api/notification.ts and restore the local session-storage subscribe helper here, unless this page intentionally needs that SSE behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/app/mockApply/`[mockApplyId]/(jd)/jd-review/page.tsx around lines
31 - 97, Remove the accidentally added subscribeToNotificationStream helper and
its SSE-related imports or references from this page. Restore a local
subscribeToSessionStorage function for the useSyncExternalStore call, preserving
sessionStorage change notifications and the existing snapshot behavior. Keep
notification SSE functionality in the existing notification API module instead.

Comment on lines +112 to +131
const fetchAndUpdateNotifications = async () => {
try {
const data = await fetchNotifications();
if (data.isSuccess && data.result) {
const mappedItems = data.result.map(mapApiToLnbItem);
setNotificationItems(mappedItems);

setNotificationItems((prevItems) => {
if (prevItems.length > 0 && mappedItems.length > 0) {
const latestNewItem = mappedItems[0];
const prevLatestItem = prevItems[0];

if (
latestNewItem.id !== prevLatestItem.id &&
!latestNewItem.read
) {
triggerToastBasedOnNotification(latestNewItem);
}
}
return mappedItems;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don't trigger side effects inside the setNotificationItems updater.

triggerToastBasedOnNotification calls setToastState and setTimeout from within the updater function. React may invoke updaters more than once (Strict Mode, re-entrancy), producing duplicate toasts and stray timers. Compute the diff outside the updater using the mapped result and a ref holding the previously seen latest id.

♻️ Proposed restructure
-          setNotificationItems((prevItems) => {
-            if (prevItems.length > 0 && mappedItems.length > 0) {
-              const latestNewItem = mappedItems[0];
-              const prevLatestItem = prevItems[0];
-
-              if (
-                latestNewItem.id !== prevLatestItem.id &&
-                !latestNewItem.read
-              ) {
-                triggerToastBasedOnNotification(latestNewItem);
-              }
-            }
-            return mappedItems;
-          });
+          const latestNewItem = mappedItems[0];
+          const prevLatestId = latestNotificationIdRef.current;
+
+          setNotificationItems(mappedItems);
+
+          if (
+            latestNewItem &&
+            prevLatestId !== null &&
+            latestNewItem.id !== prevLatestId &&
+            !latestNewItem.read
+          ) {
+            triggerToastBasedOnNotification(latestNewItem);
+          }
+
+          latestNotificationIdRef.current = latestNewItem?.id ?? null;

Add alongside the other refs:

const latestNotificationIdRef = useRef<string | null>(null);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const fetchAndUpdateNotifications = async () => {
try {
const data = await fetchNotifications();
if (data.isSuccess && data.result) {
const mappedItems = data.result.map(mapApiToLnbItem);
setNotificationItems(mappedItems);
setNotificationItems((prevItems) => {
if (prevItems.length > 0 && mappedItems.length > 0) {
const latestNewItem = mappedItems[0];
const prevLatestItem = prevItems[0];
if (
latestNewItem.id !== prevLatestItem.id &&
!latestNewItem.read
) {
triggerToastBasedOnNotification(latestNewItem);
}
}
return mappedItems;
});
const fetchAndUpdateNotifications = async () => {
try {
const data = await fetchNotifications();
if (data.isSuccess && data.result) {
const mappedItems = data.result.map(mapApiToLnbItem);
const latestNewItem = mappedItems[0];
const prevLatestId = latestNotificationIdRef.current;
setNotificationItems(mappedItems);
if (
latestNewItem &&
prevLatestId !== null &&
latestNewItem.id !== prevLatestId &&
!latestNewItem.read
) {
triggerToastBasedOnNotification(latestNewItem);
}
latestNotificationIdRef.current = latestNewItem?.id ?? null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/components/common/lnb/Lnb.tsx` around lines 112 - 131, Move the
toast side effect out of the setNotificationItems updater in
fetchAndUpdateNotifications. Track the previously seen latest notification id
with a latestNotificationIdRef ref, compare it with mappedItems[0] outside the
state setter, trigger the toast only for a new unread latest item, then update
the ref and set mappedItems without side effects.

const mappedNewItem = mapApiToLnbItem(newNotification);
setNotificationItems((prev) => [mappedNewItem, ...prev]);
setHasNotification(true);
console.log("🔥 [SSE 수신 완료] 서버에서 알림 옴!!!", newNotification);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Debug logging of full notification payloads in both LNB files. Shared root cause: raw notification objects containing user-scoped identifiers (mockApplyId, jobPostingId, taskId) are dumped to the console on the production path.

  • jobdri/src/components/common/lnb/Lnb.tsx#L176-L176: remove the SSE receive log or reduce it to the notification type/id.
  • jobdri/src/components/common/lnb/LnbNotification.tsx#L426-L426: remove the click-time full-object log or gate it behind a development check.
📍 Affects 2 files
  • jobdri/src/components/common/lnb/Lnb.tsx#L176-L176 (this comment)
  • jobdri/src/components/common/lnb/LnbNotification.tsx#L426-L426
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/components/common/lnb/Lnb.tsx` at line 176, Remove or minimize the
full notification payload logging in Lnb.tsx at
jobdri/src/components/common/lnb/Lnb.tsx:176-176, retaining only the
notification type or identifier if needed. In LnbNotification.tsx at
jobdri/src/components/common/lnb/LnbNotification.tsx:426-426, remove the
click-time full-object log or gate it behind a development-only check.

Comment on lines 443 to 457
switch (apiType) {
// #0 공고 분석 성공
case "JOB_POSTING_ASYNC_SUCCEEDED":
router.push(`/job-posting/${mockApplyId}`);
if (savedToDatabase === true && jobPostingId) {
router.push(`/mockApply/job/${jobPostingId}/review`);
} else {
console.warn("⚠️ 공고 분석은 완료되었으나 DB 저장이 보류되었습니다.");
router.push(`/mockApply/job/create?analysisError=deferred`);
}
break;

// #1 공고 분석 실패
case "JOB_POSTING_ASYNC_FAILED":
router.push(`/job-posting/${mockApplyId}`);
router.push(`/mockApply/job/create?analysisError=1`);
break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t f 'page.tsx' -p 'mockApply/job/create' --exec rg -nP -C4 'analysisError'
rg -nP -C2 "analysisError" --type=ts --type=tsx

Repository: JobDri-Developer/FrontEnd

Length of output: 299


🏁 Script executed:

#!/bin/bash
set -u

echo "## repo root"
pwd

echo "## candidate create files"
git ls-files | grep -E '(^|/)mockApply/.*/(create|page\.(jsx?|tsx))$|(^|/)LnbNotification\.tsx$|(^|/)Lnb\.tsx$' | sed -n '1,120p'

echo "## broad search analysisError (all tracked)"
rg -n -C2 'analysisError|JOB_POSTING_ASYNC_SUCCEEDED|JOB_POSTING_ASYNC_FAILED|deferred|not_saved' . || true

Repository: JobDri-Developer/FrontEnd

Length of output: 5559


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## create page relevant section"
sed -n '85,125p' jobdri/src/app/mockApply/job/create/page.tsx | cat -n

echo "## notify component relevant section"
sed -n '438,458p' jobdri/src/components/common/lnb/LnbNotification.tsx | cat -n

echo "## loading fallback relevant section"
sed -n '145,180p' jobdri/src/app/mockApply/job/loading/page.tsx | cat -n

Repository: JobDri-Developer/FrontEnd

Length of output: 3911


Use a shared analysisError constant and match the deferred DB-save path.

/mockApply/job/create explicitly opens the invalid-data modal only for analysisError=not_saved, but LnbNotification pushes analysisError=deferred and analysisError=1, so users with JOB_POSTING_ASYNC_SUCCEEDED in non-DB-saved state won’t see the modal. Align these values in one shared constant across producer and consumer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/components/common/lnb/LnbNotification.tsx` around lines 443 - 457,
The JOB_POSTING_ASYNC_SUCCEEDED deferred-save and JOB_POSTING_ASYNC_FAILED paths
use error query values that do not match the consumer’s modal trigger. Define or
reuse one shared analysisError constant for the not-saved case, update both
router.push calls in LnbNotification accordingly, and ensure the
/mockApply/job/create consumer uses the same constant.

@minnngo minnngo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고많앗아~~

@yiyoonseo
yiyoonseo merged commit 26ab2c8 into develop Jul 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat: 알림 API 연동

2 participants